﻿using System;
using System.Text;

namespace Cobalt.Framework
{
	/// <summary>
	/// Wraps a source of characters.
	/// </summary>
	/// <remarks>
	/// This type adds support for handling
	/// subranges of the given source.
	/// </remarks>
	public struct TextSource
	{
		private string stringSource;
		private TextBuffer textSource;
		private char[] charsSource;
		private StringBuilder builderSource;

		private int index, count;

		/// <summary>
		/// Gets the character at the given index in the text source.
		/// </summary>
		/// <param name="idx">The index of the character to retrieve.</param>
		/// <returns>The indexed character.</returns>
		public char this[int idx]
		{
			get
			{
				if( idx >= count )
					throw new ArgumentOutOfRangeException();

				idx += index;

				if( stringSource != null )
					return stringSource[idx];

				if( textSource != null )
					return textSource.Buffer[idx];

				if( charsSource != null )
					return charsSource[idx];

				if( builderSource != null )
					return builderSource[idx];

				return (char)0;
			}
		}

		/// <summary>
		/// Gets the number of characters in the text source.
		/// </summary>
		public int Count { get { return count; } }

		public TextSource Substring( int index, int count )
		{
			if( index < 0 || count < 0 || index + count > this.count )
				throw new ArgumentOutOfRangeException();

			TextSource ret = this;
			ret.index += index;
			ret.count = count;

			return ret;
		}

		public TextSource Substring( int index )
		{
			return Substring( index, count - index );
		}

		/// <summary>
		/// Initialize a text source with a subrange of a <see cref="string"/>.
		/// </summary>
		/// <param name="text">The <see cref="string"/> to initialize with.</param>
		/// <param name="index">The index of the first character to include in the subrange.</param>
		/// <param name="count">The number of characters in the subrange.</param>
		public TextSource( string text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException();
			if( index < 0 || count < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			this = default( TextSource );

			this.stringSource = text;

			this.index = index;
			this.count = count;
		}

		/// <summary>
		/// Initialize a text source with a <see cref="string"/>.
		/// </summary>
		/// <param name="text">The <see cref="string"/> to initialize with.</param>
		public TextSource( string text )
		{
			if( text == null )
				throw new ArgumentNullException();

			this = default( TextSource );

			this.stringSource = text;

			this.index = 0;
			this.count = text.Length;
		}

		/// <summary>
		/// Initialize a text source with a subrange of a <see cref="TextBuffer"/>.
		/// </summary>
		/// <param name="text">The <see cref="TextBuffer"/> to initialize with.</param>
		/// <param name="index">The index of the first character to include in the subrange.</param>
		/// <param name="count">The number of characters in the subrange.</param>
		public TextSource( TextBuffer text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException();
			if( index < 0 || count < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			this = default( TextSource );

			this.textSource = text;

			this.index = index;
			this.count = count;
		}

		/// <summary>
		/// Initialize a text source with a <see cref="TextBuffer"/>.
		/// </summary>
		/// <param name="text">The <see cref="TextBuffer"/> to initialize with.</param>
		public TextSource( TextBuffer text )
		{
			if( text == null )
				throw new ArgumentNullException();

			this = default( TextSource );

			this.textSource = text;

			this.index = 0;
			this.count = text.Length;
		}

		/// <summary>
		/// Initialize a text source with a subrange of a <see cref="char[]"/>.
		/// </summary>
		/// <param name="text">The <see cref="char[]"/> to initialize with.</param>
		/// <param name="index">The index of the first character to include in the subrange.</param>
		/// <param name="count">The number of characters in the subrange.</param>
		public TextSource( char[] text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException();
			if( index < 0 || count < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			this = default( TextSource );

			this.charsSource = text;

			this.index = index;
			this.count = count;
		}

		/// <summary>
		/// Initialize a text source with a <see cref="char[]"/>.
		/// </summary>
		/// <param name="text">The <see cref="char[]"/> to initialize with.</param>
		public TextSource( char[] text )
		{
			if( text == null )
				throw new ArgumentNullException();

			this = default( TextSource );

			this.charsSource = text;

			this.index = 0;
			this.count = text.Length;
		}

		/// <summary>
		/// Initialize a text source with a <see cref="ArraySegment<char>"/>.
		/// </summary>
		/// <param name="text">The <see cref="ArraySegment<char>"/> to initialize with.</param>
		public TextSource( ArraySegment<char> text )
		{
			if( text.Array == null )
				throw new ArgumentNullException();

			this = default( TextSource );

			this.charsSource = text.Array;

			this.index = text.Offset;
			this.count = text.Count;
		}

		/// <summary>
		/// Initialize a text source with a subrange of a <see cref="StringBuilder"/>.
		/// </summary>
		/// <param name="text">The <see cref="StringBuilder"/> to initialize with.</param>
		/// <param name="index">The index of the first character to include in the subrange.</param>
		/// <param name="count">The number of characters in the subrange.</param>
		public TextSource( StringBuilder text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException();
			if( index < 0 || count < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			this = default( TextSource );

			this.builderSource = text;

			this.index = index;
			this.count = count;
		}

		/// <summary>
		/// Initialize a text source with a <see cref="StringBuilder"/>.
		/// </summary>
		/// <param name="text">The <see cref="StringBuilder"/> to initialize with.</param>		
		public TextSource( StringBuilder text )
		{
			if( text == null )
				throw new ArgumentNullException();

			this = default( TextSource );

			this.builderSource = text;

			this.index = 0;
			this.count = text.Length;
		}

		/// <summary>
		/// A text source representing the empty string.
		/// </summary>
		public static readonly TextSource Empty;

		/// <summary>
		/// Converts a <see cref="string"/> into a text source.
		/// </summary>
		/// <param name="text">The <see cref="string"/> to convert.</param>
		/// <returns>
		/// A text source wrapping <paramref name="text"/>,
		/// or the <see cref="Empty"/> text source if
		/// <paramref name="text"/> is null.
		/// </returns>
		public static implicit operator TextSource( string text )
		{
			return text != null ? new TextSource( text ) : Empty;
		}

		/// <summary>
		/// Converts a <see cref="TextBuffer"/> into a text source.
		/// </summary>
		/// <param name="text">The <see cref="TextBuffer"/> to convert.</param>
		/// <returns>
		/// A text source wrapping <paramref name="text"/>,
		/// or the <see cref="Empty"/> text source if
		/// <paramref name="text"/> is null.
		/// </returns>
		public static implicit operator TextSource( TextBuffer text )
		{
			return text != null ? new TextSource( text ) : Empty;
		}

		/// <summary>
		/// Converts a <see cref="char[]"/> into a text source.
		/// </summary>
		/// <param name="text">The <see cref="char[]"/> to convert.</param>
		/// <returns>
		/// A text source wrapping <paramref name="text"/>,
		/// or the <see cref="Empty"/> text source if
		/// <paramref name="text"/> is null.
		/// </returns>
		public static implicit operator TextSource( char[] text )
		{
			return text != null ? new TextSource( text ) : Empty;
		}

		/// <summary>
		/// Converts a <see cref="ArraySegment<char>"/> into a text source.
		/// </summary>
		/// <param name="text">The <see cref="ArraySegment<char>"/> to convert.</param>
		/// <returns>
		/// A text source wrapping <paramref name="text"/>,
		/// or the <see cref="Empty"/> text source if
		/// <paramref name="text"/> is null.
		/// </returns>
		public static implicit operator TextSource( ArraySegment<char> text )
		{
			return text != null ? new TextSource( text ) : Empty;
		}

		/// <summary>
		/// Converts a <see cref="StringBuilder"/> into a text source.
		/// </summary>
		/// <param name="text">The <see cref="StringBuilder"/> to convert.</param>
		/// <returns>
		/// A text source wrapping <paramref name="text"/>,
		/// or the <see cref="Empty"/> text source if
		/// <paramref name="text"/> is null.
		/// </returns>
		public static implicit operator TextSource( StringBuilder text )
		{
			return text != null ? new TextSource( text ) : Empty;
		}

		public static bool operator ==( TextSource a, TextSource b )
		{
			return a.Equals( b );
		}

		public static bool operator !=( TextSource a, TextSource b )
		{
			return !a.Equals( b );
		}

		/// <summary>
		/// Compares the value of this source to another.
		/// </summary>
		/// <param name="other"></param>
		/// <returns></returns>
		/// <remarks>
		/// This comparison is based on the value of the strings,
		/// so a <see cref="TextSource"/> wrapping, for instance,
		/// a <see cref="string"/> and another wrapping a 
		/// <see cref="StringBuilder"/> with the same value will
		/// compare equal.
		/// </remarks>
		public bool Equals( TextSource other )
		{
			if( count != other.count )
				return false;

			for( int i = 0; i < count; i++ )
			{
				if( this[i] != other[i] )
					return false;
			}

			return true;
		}

		public override bool Equals( object obj )
		{
			return obj is TextSource && Equals( (TextSource)obj );
		}

		public override int GetHashCode()
		{
			int ret = 0;

			for( int i = 0; i < count; i++ )
			{
				char ch = this[i];
				ret = (ret << 5) - ret + ch; //where do I know this from? do I need to credit someone? >.<
			}

			return ret;
		}

		public override string ToString()
		{
			if( stringSource != null )
				return stringSource.Substring( index, count );

			if( builderSource != null )
				return builderSource.ToString( index, count );

			var sb = new StringBuilder( count );
			for( int i = 0; i < count; i++ )
				sb.Append( this[i] );

			return sb.ToString();
		}
	}
}